<template>
  <div>
    <div
      class="container"
      draggable="true"
      @dragenter="dragFn($event)"
      @dragover="dragFn($event)"
      @drop="dropFn"
      @click="handleClick"
    >
      <video v-if="fileExtension == 'mp4'" :src="fileUrl" controls></video>
      <img v-else-if="fileExtension == 'png'" :src="fileUrl" alt="" />
      <IconTooling v-else></IconTooling>
    </div>
    <el-button
      type="primary"
      v-if="upLoadState == STATE.WAIT"
      @click="handleUpload"
      >上传</el-button
    >
    <div v-loading="fileHashCreating" v-if="fileHashCreating">文件解析中</div>
    <el-button
      type="primary"
      v-if="upLoadState == STATE.UPLOADING && !fileHashCreating"
      @click="handlePause"
      >暂停</el-button
    >
    <el-button
      type="primary"
      v-if="upLoadState == STATE.PAUSE"
      @click="handleUpload"
      >恢复上传</el-button
    >
    <div class="process">
      <span> 总进度 </span>
      <el-progress :percentage="totalPercentage" />
    </div>
    <div class="progress" v-for="(item, index) in chunksPercent">
      <span> 切片{{ index + 1 }} </span>
      <el-progress :key="index" :percentage="item.percent" />
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed } from "vue";
import { ElMessage } from "element-plus";
import IconTooling from "./icons/IconTooling.vue";
import axios from "axios";
// import SparkMd5 from "spark-md5";
import service from "../utils/request";

//定义常量
const CHUNK_SIZE = 1024 * 1024 * 5; // 每个切片的大小为5MB
const MAX_SIZE = 1024 * 1024 * 1024 * 2; // 文件最大size为2G
//状态
const STATE = {
  WAIT: "wait",
  UPLOADING: "uploading",
  PAUSE: "pause",
};

function dragFn(e) {
  e.preventDefault();
}
let selectFile = null;
let fileUrl = ref("");
let fileExtension = ref("");
let filename = ref("");
let fileInfo = reactive({});
let chunksPercent = ref([]);
let upLoadState = ref(STATE.WAIT);
let totalPercentage = computed(() => {
  if (!chunksPercent.value.length) {
    return 0;
  }
  let total = chunksPercent.value.reduce((acc, cur) => {
    return Number(acc) + Number(cur.percent);
  }, 0);
  return Math.round(total / chunksPercent.value.length);
});

//暂停上传
function handlePause() {
  //修改状态
  upLoadState.value = STATE.PAUSE;
  newCancelToken.map((item) => {
    item.cancel("请求被用户取消");
  });
  console.log("用户取消请求");
}

function dropFn(e) {
  e.preventDefault();
  // 获取拖拽过来的文件
  const files = e.dataTransfer.files;
  // 处理文件
  console.log(files);
  setSelectFile(files[0]);
  //预览文件
  previewFile(selectFile);
}
//预览文件 将文件转换为实际URL

function previewFile(file) {
  const url = window.URL.createObjectURL(file);
  console.log(url);
  fileUrl.value = url;
  //取消URL
  // window.URL.revokeObjectURL(url);
}

//  保存选择的file
function setSelectFile(file) {
  selectFile = file;
  fileInfo = {
    name: file.name,
    size: file.size,
    hash: "",
  };

  let fileName = file.name;
  // 获取文件后缀名
  fileExtension.value = fileName.split(".").pop();
  console.log(fileExtension);
}
let fileHashCreating = ref(false);
async function fileVerify() {
  let { hash, name } = fileInfo;
  return await service.post("/verify", {
    fileHash: hash,
    fileName: name,
  });
}

async function handleUpload() {
  //清空进度条
  if (!selectFile) {
    ElMessage.warning("请选择文件");
    return;
  }
  //检验文件大小 和type
  if (!checkFile(selectFile)) {
    return;
  }

  //  通过web worker获取hash
  function calcHash(chunks) {
    return new Promise((resolve, reject) => {
      let hashWork = new Worker("/hash.js");
      fileHashCreating.value = true;
      hashWork.postMessage({ chunks });
      hashWork.onmessage = (e) => {
        let { hash } = e.data;
        fileHashCreating.value = false;
        if (hash) {
          resolve(hash);
        }
      };
    });
  }

  // 生成切片
  const chunks = createChunks(selectFile);
  // 切片文件 累加生成文件hash

  let fileHash = await calcHash(chunks);
  fileInfo.hash = fileHash;
  filename.value = `${fileHash}.${fileExtension.value}`;
  fileInfo.fileName = filename.value;
  let chunkList = chunks.map((item, index) => {
    return {
      ...item,
      chunkName: `${fileHash}-${index}`,
    };
  });
  console.log("fileHash", fileHash);
  //校验文件是否已经上传过
  let {
    data: { shouldUpload, uploadedList },
  } = await fileVerify();
  if (!shouldUpload) {
    ElMessage.warning("文件已经上传过");
    return;
  } else if (shouldUpload) {
    // if (uploadedList.length === 0) {
    //代表沒有上传过切片 正常走流程上传
    //拼接fileName 与 chunkname =>chunkname为filename+索引
    let filterChunks = chunkList.filter((part) => {
      let uploadedFile = uploadedList.find((item) => item === part.chunkName);
      return !uploadedFile;
    });
    console.log(chunks.length);
    console.log(filterChunks.length);
    //重置已经上传的切片的结果
    uploadChunkResult = [];
    if (filterChunks.length === 0) {
      //代表全都上传过了  直接调用合并切片接口
      mergeFile(fileInfo.name, fileInfo.hash);
      return false;
    }
    let newChunks = filterChunks.map((part, index) => {
      return {
        chunk: part.chunk,
        size: part.size,
        fileName: `${filename.value}`,
        chunkName: part.chunkName,
        chunkNumber: chunks.length,
      };
    });
    chunksPercent.value = newChunks.map(() => ({ percent: 0 }));
    upLoadState.value = STATE.UPLOADING;
    //上传切片
    // 控制并发数上传
    await uploadChunks(newChunks);
    // let res = await Promise.all(requestList);
    console.log(uploadChunkResult);
    if (uploadChunkResult.every((item) => item?.code === 0)) {
      //调用合并切片接口
      mergeFile(fileInfo.name, fileInfo.hash);
    }
  }
}
// 控制并发数上传
const Max_REQUEST_COUNT = 3; //最大并发数
let currentRequests = 0; // 当前并发数
const uploadRequestList = []; //上传请求列表
let uploadChunkResult = [];
async function processQueue() {
  while (currentRequests < Max_REQUEST_COUNT && uploadRequestList.length > 0) {
    const request = uploadRequestList.shift();
    currentRequests++;
    request()
      .then((res) => {
        uploadChunkResult.push(res);
        currentRequests--;
        processQueue();
      })
      .catch((error) => {
        currentRequests--;
        console.error(error);
        processQueue();
      });
  }
}
// 合并切片接口
function mergeFile(fileName, fileHash) {
  // chunkSize;
  service({
    url: `/merge`,
    method: "POST",
    data: {
      fileName,
      fileHash,
      chunkSize: CHUNK_SIZE,
    },
  }).then((res) => {
    upLoadState.value = STATE.WAIT;

    console.log(res);
  });
}

let newCancelToken = [];
function uploadChunks(chunks) {
  chunks.map((part, index) => {
    let formData = new FormData();
    formData.append("fileHash", fileInfo.hash);
    formData.append("fileSize", String(fileInfo.size));
    formData.append("fileName", fileInfo.fileName);
    formData.append("index", String(index));
    formData.append("chunkFile", part.chunk);
    formData.append("chunkHash", part.chunkName);
    formData.append("chunkNumber", part.chunkNumber);
    formData.append("chunkSize", String(part.size));
    //中断请求 使用axios的cancelToken
    const CancelToken = axios.CancelToken.source();
    newCancelToken.push(CancelToken);
    uploadRequestList.push(
      service.bind(this, {
        url: `/upload`,
        method: "POST",
        data: formData,
        headers: {
          "Content-Type": "multipart/form-data",
        },
        onUploadProgress: (e) => {
          let percent = Math.floor((e.loaded * 100) / e.total);
          if (percent === 100) {
            newCancelToken.splice(index, 1);
          }
          chunksPercent.value[index].percent = percent;
        },
        cancelToken: CancelToken.token,
      })
    );
    processQueue();
  });
}
// div click 模拟 input type=file 点击
function handleClick() {
  let input = document.createElement("input");
  input.type = "file";
  input.style.display = "none";
  document.body.appendChild(input);
  input.click();
  input.onchange = (e) => {
    let file = e.target.files[0];
    if (file) {
      setSelectFile(file);
      //预览文件
      previewFile(file);
    }
  };
}

function createChunks(file) {
  let chunks = [];
  //循环切片
  //切割次数
  let splitNum = Math.ceil(file.size / CHUNK_SIZE);
  for (let i = 0; i < splitNum; i++) {
    let chunk = file.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
    chunks.push({
      chunk,
      size: chunk.size,
    });
  }
  return chunks;
}

function checkFile(file) {
  if (file.size > MAX_SIZE) {
    ElMessage.warning("文件大小超过限制");
    return false;
  }
  // // 检查文件类型
  // const allowedTypes = ["image/jpeg", "image/png", "image/gif"];
  // let fileType = file.type;
  // if (!(fileType.startsWith("image") || fileType.startsWith("video"))) {
  //   ElMessage.warning("不支持的文件类型");
  //   return false;
  // }
  return true;
}
</script>

<style scoped>
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100px;
  border: 1px solid #ccc;
  border-radius: 5px;
  cursor: pointer;
  &:hover {
    border: 1px solid #bbb;
  }
  img,
  video {
    width: auto;
    height: 100%;
  }
}
.btn {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  line-height: 1;
  height: 32px;
  white-space: nowrap;
  cursor: pointer;
  color: #fff;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  transition: 0.1s;
  -webkit-user-select: none;
  user-select: none;
  vertical-align: middle;
  background-color: #409eff;
  border: none;
  margin-top: 10px;
  border-radius: 5px;
  padding: 10px;
  width: 50px;
}
</style>
